package com.huitongjy.common;

import com.huitongjy.common.zookeeper.ZooKeeperWorkId;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;

/**
 * <p>
 *      参考 snowflake 自定义的taskNo生成规则 [P|G] + yyMMddHHmmss + 0000000
 * </p>
 *
 * <pre>
 * 13bit 机器Id 支持 8 * 1024 = 8196 机器位
 * 10bit 自增序列 支持时间内1024条自增
 * </pre>
 *
 * @author guan
 */
public class TaskNoGenerator {

    /**
     * 自增序列位 10 (1024)
     */
    private int sequenceBits = 10;

    /**
     * 机器位 13 (16 * 512)
     */
    private int workerIdBits = 13;

    /**
     * 自增序列位限制位 通过与操作确保不会溢出
     */
    private int sequenceMask = (1 << sequenceBits) - 1;

    /**
     * 机器Id偏移
     */
    private int workerIdLeftShiftBits = sequenceBits;

    private long sequence;

    private long lastTime;

    private long workerId;

    //todo guan 优化
    private DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
    private DecimalFormat numFormat = new DecimalFormat("0000000");

    private TaskNoGenerator() {
        long workerId = ZooKeeperWorkId.getInstanse().getGlobalWorkerId();
        if(workerId < 0L || workerId >= (1L << workerIdBits)) {
            throw new RuntimeException("机器ID过大");
        }
        this.workerId = workerId;
    }

    private static TaskNoGenerator INSTANCE;

    public static synchronized TaskNoGenerator getInstanse() {
        if(INSTANCE==null) {
            INSTANCE = new TaskNoGenerator();
        }
        return INSTANCE;
    }

    /**
     * Generate key.
     *
     * @return key type is @{@link Long}.
     */
    public synchronized String generateNo(boolean isExam) {
        long currentMillis = this.timeGen();
        if(currentMillis<=lastTime) {
            if (++sequence > sequenceMask) {
                sequence = 0;
                lastTime = waitUntilNextTime(lastTime);
            }
        } else {
            lastTime = currentMillis;
            sequence = 0;
        }

        return (isExam?"P":"G") + dateFormat.format (lastTime) + numFormat.format((workerId << workerIdLeftShiftBits) | sequence);
    }

    private long waitUntilNextTime(final long lastTime) {
        long time = this.timeGen();
        while (time <= lastTime) {
            time = this.timeGen();
        }
        return time;
    }

    private long timeGen() {
        return System.currentTimeMillis()/1000 * 1000;
    }
}
